<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>模拟光照让3D场景更逼真（下）-小试牛刀</title>
    <style>
        canvas{
            display: block;
            border: 1px solid #ccc;
        }
    </style>
</head>
<body>
    <canvas width="512" height="512"></canvas>

    <script type="module">
        import { Renderer, Camera, Transform, Box, Sphere, Cylinder, Torus, Program, Mesh, Color } from 'https://unpkg.com/ogl';
        import { Phone } from '/review/common/lib/phone.js';
        import { Material } from '/review/common/lib/material.js';

        /**
         * OGL用法
         * 1.创建renderer对象
         * 2.创建相机
         * 3.创建场景
         * 4.创建几何体对象
         * 5.创建webgl程序
         * 6.设置网格对象
         * 7.完成渲染
         * */ 

        const canvas = document.querySelector("canvas");
        //  1.创建renderer对象
        const renderer = new Renderer({
            canvas,
            width:512,
            height:512,
        });
        const gl = renderer.gl;
        gl.clearColor(1, 1, 1, 1);

        // 2.创建相机
        const camera = new Camera(gl, {
            fov:35
        });
        // 默认透视投影
        const cameraPosition = [0, 0, 8];
        camera.position.set(...cameraPosition);
        camera.lookAt([0,0,0]);

        // 3.创建场景
        const scene = new Transform();

        // 4.创建几何体对象
        // 立方体
        const boxGeometry = new Box(gl);
        // 球体
        const sphereGeometry = new Sphere(gl);
        // 圆柱体
        const cylinderGeometry = new Cylinder(gl);
        // 圆环体
        const torusGeometry = new Torus(gl);

        // 5.创建webgl程序
        const vertex = `
            attribute vec3 position;
            attribute vec3 normal;

            uniform mat4 projectionMatrix;
            uniform mat4 modelViewMatrix;
            uniform mat3 normalMatrix;

            varying vec3 vNormal;
            varying vec4 vPos;
            
            void main() {
                vPos = modelViewMatrix * vec4(position, 1.0);

                vNormal = normalize(normalMatrix * normal);

                gl_Position = projectionMatrix * vPos;
            }
        `;
        const fragment = `
            #ifdef GL_ES
            precision mediump float;
            #endif
            #define MAX_NUM 16
            
            varying vec3 vNormal;
            varying vec4 vPos;
            
            uniform mat4 viewMatrix;
            uniform vec3 cameraPosition;

            // 材质
            uniform vec3 materialReflection;
            uniform float specularFactor;
            uniform float shininess;

            // 环境光
            uniform vec3 ambientLight;

            // 平行光属性
            uniform vec3 directionalLightDirection[MAX_NUM];
            uniform vec3 directionalLightColor[MAX_NUM];

            // 点光源属性
            uniform vec3 pointLightPosition[MAX_NUM];
            uniform vec3 pointLightColor[MAX_NUM];
            uniform vec3 pointLightDecay[MAX_NUM];

            // 聚光灯属性
            uniform vec3 spotLightPosition[MAX_NUM];
            uniform vec3 spotLightDirection[MAX_NUM];
            uniform vec3 spotLightColor[MAX_NUM];
            uniform vec3 spotLightDecay[MAX_NUM];
            uniform float spotLightAngle[MAX_NUM];

            // 探照灯属性
            uniform vec3 searchLightPosition[MAX_NUM];
            uniform vec3 searchLightDirection[MAX_NUM];
            uniform vec3 searchLightColor[MAX_NUM];
            uniform vec3 searchLightDecay[MAX_NUM];
            uniform float searchLightRadius[MAX_NUM];

            // 反射光强度
            float getSpecular(vec3 dir, vec3 normal, vec3 eye){
                // 求出反射光向量
                vec3 reflectionLight = reflect(-dir, normal);
                // 求反射光向量与视线向量的余弦
                float eyeCos = max(dot(eye, reflectionLight),0.0);
                return specularFactor * pow(eyeCos, shininess);
            }   

            // phong反射模型计算
            vec4 phongReflection(vec3 pos, vec3 normal, vec3 eye){
                float specular = 0.0;
                vec3 diffuse = vec3(0.0);
                // 处理平行光
                for(int i = 0; i < MAX_NUM; i++){
                    vec3 dDir = directionalLightDirection[i];
                    if(dDir.x==0.0 && dDir.y==0.0 && dDir.z==0.0) continue;
                    dDir = (viewMatrix * vec4(dDir, 0.0)).xyz;
                    vec3 dir = normalize(-dDir);
                    // 光源强度
                    float vCos = max(dot(normal, dir), 0.0);
                    // 计算漫反射
                    diffuse += vCos * directionalLightColor[i].xyz;
                    // 镜面反射强度
                    specular += getSpecular(dir, normal, eye);
                }

                // 处理点光源
                for(int i = 0; i < MAX_NUM; i++){
                    vec3 pDecay = pointLightDecay[i];
                    if(pDecay.x==0.0 && pDecay.y==0.0 && pDecay.z==0.0) continue;
                    // 点光源与顶点形成的向量
                    vec3 vDir = (viewMatrix * vec4(pointLightPosition[i].xyz, 1.0)).xyz - pos;
                    // 点光源到顶点的距离
                    float dis = length(vDir);
                    vec3 dir = normalize(vDir);
                    // 计算衰减
                    float decay = min(1.0, 1.0 / (pDecay.x * pow(dis, 2.0) + pDecay.y * dis + pDecay.z));
                    
                    // 光源强度
                    float vCos = max(dot(normal, dir), 0.0);
                    // 计算漫反射
                    diffuse += decay * vCos * pointLightColor[i].xyz;
                    specular += getSpecular(dir, normal, eye);
                }
                
                // 处理聚光灯
                for(int i = 0; i < MAX_NUM; i++){
                    vec3 sDecay = spotLightDecay[i];
                    if(sDecay.x==0.0 && sDecay.y==0.0 && sDecay.z==0.0) continue;
                    // 顶点形成的向量
                    vec3 vDir = (viewMatrix * vec4(spotLightPosition[i], 1.0)).xyz - pos;
                    // 到顶点的距离
                    float dis = length(vDir);
                    vec3 dir = normalize(vDir);
                    // 计算衰减
                    float decay = min(1.0, 1.0 / (sDecay.x * pow(dis, 2.0) + sDecay.y * dis + sDecay.z));

                    // 判断角度范围
                    float c = cos(spotLightAngle[i]);
                    vec3 angle = (viewMatrix * vec4(spotLightDirection[i], 0.0)).xyz;
                    float d = step(c, dot(-dir, normalize(angle)));

                    // 光源强度
                    float vCos = max(dot(normal, dir), 0.0);
                    // 计算漫反射
                    diffuse += d * decay * vCos * spotLightColor[i];
                    specular += d * getSpecular(dir, normal, eye);
                }

                // 处理探照灯
                for(int i = 0; i < MAX_NUM; i++){
                    vec3 sDecay = searchLightDecay[i];
                    if(sDecay.x==0.0 && sDecay.y==0.0 && sDecay.z==0.0) continue;

                    // 方向向量
                    vec3 direct = (viewMatrix * vec4(searchLightDirection[i], 0.0)).xyz;
                    // 到顶点的距离
                    float dis = length(direct);
                    // 方向的单位向量
                    vec3 dir = normalize(direct);
                    // 计算衰减
                    float decay = min(1.0, 1.0 / (sDecay.x * pow(dis, 2.0) + sDecay.y * dis + sDecay.z));

                    // 判断半径范围
                    // 坐标点与顶点形成的向量
                    vec3 vDir = (viewMatrix * vec4(searchLightPosition[i], 1.0)).xyz - pos;
                    // 计算方向向量与定点向量的叉乘的模（dir为单位向量，vDir为原始长度向量，此时模就是截面半径的大小）
                    float vRadius = length(cross(vDir, dir));
                    float d = step(vRadius, searchLightRadius[i]);

                    // 光源强度
                    float vCos = max(dot(normal, dir), 0.0);
                    // 计算漫反射
                    diffuse += d * decay * vCos * searchLightColor[i];
                    specular += d * getSpecular(dir, normal, eye);
                }
                
                return vec4(diffuse, specular);
            }

            void main() {
                // 根据相机位置计算对每个点的视线的向量方向
                vec3 eyeDirection = normalize(cameraPosition - vPos.xyz);  
                vec4 phong = phongReflection(vPos.xyz, vNormal, eyeDirection);
                // 环境光
                vec3 lightColor = phong.w + (ambientLight + phong.xyz) * materialReflection;

                gl_FragColor.rgb = lightColor;
                gl_FragColor.a = 1.0;
            }
        `;
        // 添加光模型
        const phong = new Phone([0.5,0.5,0.5]);

        // 添加聚光灯
        // phong.addLight({
        //     position: [3, 3, 0],
        //     direction: [-1, -1, 0],
        //     angle: Math.PI / 12,
        //     color: [1, 1, 1],
        // });
        // 添加探照灯
        phong.addLight({
            position: [0, 1, 7],
            direction: [0, 0, 7],
            radius: 0.2,
            color: [1, 1, 1],
        });
        // 添加材质
        const material1 = new Material(new Color("#0000ff"), 2.0);
        const material2 = new Material(new Color("#ff00ff"), 2.0);
        const material3 = new Material(new Color("#008000"), 2.0);
        const material4 = new Material(new Color("#ff0000"), 2.0);
        
        const light = {
            ...phong.uniforms,
            cameraPosition:{ value: cameraPosition }
        }
        console.log(light)
        const program1 = new Program(gl, {
            vertex,
            fragment,
            uniforms:{
                ...light,
                ...material1.uniforms
            }
        });
        const program2 = new Program(gl, {
            vertex,
            fragment,
            uniforms:{
                ...light,
                ...material2.uniforms
            }
        });
        const program3 = new Program(gl, {
            vertex,
            fragment,
            uniforms:{
                ...light,
                ...material3.uniforms
            }
        });
        const program4 = new Program(gl, {
            vertex,
            fragment,
            uniforms:{
                ...light,
                ...material4.uniforms
            }
        });

        // 6.设置网格对象
        const boxMesh = new Mesh(gl, {geometry:boxGeometry, program:program1});
        boxMesh.position.set(0, 1.3, 0);
        boxMesh.setParent(scene);

        const sphereMesh = new Mesh(gl, {geometry:sphereGeometry, program:program2});
        sphereMesh.position.set(1.3, 0, 0);
        sphereMesh.setParent(scene);

        const cylinderMesh = new Mesh(gl, {geometry:cylinderGeometry, program:program3});
        cylinderMesh.position.set(0, -1.3, 0);
        cylinderMesh.setParent(scene);

        const torusMesh = new Mesh(gl, {geometry:torusGeometry, program:program4});
        torusMesh.position.set(-1.3, 0, 0);
        torusMesh.setParent(scene);

        // 7. 完成渲染
        function update(t){
            boxMesh.rotation.y -= 0.01;
            sphereMesh.rotation.y -= 0.02;
            cylinderMesh.rotation.y -= 0.03;
            torusMesh.rotation.y -= 0.04;
            renderer.render({scene, camera});
            requestAnimationFrame(update);
        }
        update();
    </script>
</body>
</html>